# 新建项目中, 该处的配置, 按项目重新配置
import time
from abc import ABCMeta
from typing import Dict

from mg_app_framework import (IdiServerConfigBasic, InitFuncBasic, RabbitMQConfigBasic, TaskKey,
                              set_store, update_context)
from mg_app_framework.components import TASKS
from mg_app_framework.components.idi_server import MongodbConnectError
from mg_app_framework.components.rabbitmq_connector import RabbitMQClient, is_multi_tenant
from mg_app_framework.config import (Store, get_logger, get_organization, get_organization_all,
                                     get_uuid, set_handler, set_init_task)
from common.basic_redis import RedisClient, RedisConnectError
from prod_ctrl_service.config import ConfigStore
from tornado import gen
from tornado.testing import AsyncTestCase


class RabbitMQConfig(RabbitMQConfigBasic):
    def get_rabbitmq_host(self):
        return "192.168.20.143"

    def get_rabbitmq_port(self):
        return "5672"

    def get_rabbitmq_username(self):
        return "rabbitmq"

    def get_rabbitmq_password(self):
        return "realtech@123"

    def ack_before_message_process_func(self):
        """
        设置, mq消息处理返回ack的时机
        :return:
            True: 在处理mq消息之前, 返回ack
            False: 处理mq消息之后, 返回ack
        """
        return False

    def get_rabbitmq_subscribe_list(self):
        """
        :return:返回订阅的数组
        [
            'kkk', # 消息索引
            'yyy', # 消息索引
            ...
        ]
        """
        return []

    def message_process_func(self):
        """
        1. subscribe_list is not empty
        2. when receive message, call this function to process message
        message 数据格式:
        {
            'key'  : 'kkk',  # 消息索引
            'data' : {}      # 消息数据
        }
        """
        return {}


class RedisConfigBasic(metaclass=ABCMeta):
    def get_redis_host(self):
        return "192.168.20.229"

    def get_redis_port(self):
        return 6379

    def get_redis_username(self):
        pass

    def get_redis_password(self):
        return "realtech@123"

    def reconnect_interval(self):
        """
        :return:重连redis时间间隔, 默认 5 秒

        """
        return 5

    def get_redis_conn_pool_config(self) -> Dict:
        return {"socket_connect_timeout": 5, "max_connections": 50}


class IdiConfigBasicStore(IdiServerConfigBasic):
    # 武汉测试的mongo连接地址
    def get_mongodb_host(self):
        return "192.168.20.120"

    def get_mongodb_port(self):
        return 27010

    def retry_interval(self):
        return 5


class InitFuncConfig(InitFuncBasic):
    async def init_func(self):
        pass


async def redis_async_connect(re_conn=False, conn_time=None):
    __cnt = 0
    while True:
        try:
            if conn_time and __cnt == int(conn_time):
                get_logger().error(
                    "redis async connect error,connect more than " + str(conn_time) + " times"
                )
                raise RedisConnectError
            store = Store.get_init_task_config(TaskKey.redis_async)
            organization_name = get_organization()
            host = store.get_redis_host()
            port = int(store.get_redis_port())
            username = store.get_redis_username()
            password = store.get_redis_password()
            reconnect_interval = store.reconnect_interval()
            redis_conn_pool_config = store.get_redis_conn_pool_config()
            redis_handler = RedisClient(
                host,
                port,
                username,
                password,
                organization_name,
                reconnect_interval,
                redis_conn_pool_config,
            )
            connector = await redis_handler.get_connection()
            set_handler(TaskKey.redis_async, connector)
            return connector
        except Exception:
            __cnt += 1
            get_logger().exception("redis async connecting retry number: " + str(__cnt))
            store = Store.get_init_task_config(TaskKey.redis_async)
            await gen.sleep(store.reconnect_interval())


async def rabbitmq_async_connect(conn_time=None):
    # 　初始化连接配置
    if is_multi_tenant():
        topic_list = [organization.get("org_code") for organization in get_organization_all()]
    else:
        topic_list = [get_organization()]

    store = Store.get_init_task_config(TaskKey.rabbitmq_async)
    host = store.get_rabbitmq_host()
    port = int(store.get_rabbitmq_port())
    username = store.get_rabbitmq_username()
    password = store.get_rabbitmq_password()
    publish_config_data = store.publish_config_data()
    subscribe_list = store.get_rabbitmq_subscribe_list()
    queue_name = get_uuid()
    message_process_func = store.message_process_func()
    database_for_persist_message = store.set_database_for_persist_message()
    sqlite_db_file_path = store.set_sqlite_db_file_path()
    republish_fetch_message_num = store.republish_fetch_message_num()
    alarm_threshold = store.set_disk_usage_alarm_threshold()
    reconnect_interval = store.reconnect_interval()
    heartbeat_interval = store.heartbeat_interval()
    qos_num = store.set_qos_num()
    ack_before_message_process_func = store.ack_before_message_process_func()
    publish_timeout = store.publish_timeout()

    rabbitmq_handler = RabbitMQClient(
        host,
        port,
        username,
        password,
        topic_list,
        queue_name,
        subscribe_list,
        publish_config_data,
        database_for_persist_message,
        sqlite_db_file_path,
        reconnect_interval,
        heartbeat_interval,
        republish_fetch_message_num,
        alarm_threshold,
        qos_num,
        ack_before_message_process_func,
        publish_timeout,
        message_process_func,
    )

    await rabbitmq_handler.create_connection_to_database_for_persist()
    await rabbitmq_handler.read_persist_message_to_cache()

    # 建立连接
    await rabbitmq_handler.get_connection()  # create connection
    set_handler(TaskKey.rabbitmq_async, rabbitmq_handler)
    if rabbitmq_handler.connection:
        await rabbitmq_handler.init_topic_and_queue()
        await rabbitmq_handler.send_subscribe_key_list_to_publisher(subscribe_list, topic_list)

    # start_next_task(TaskKey.rabbitmq_async)
    loop = Store.get_loop()
    loop.spawn_callback(rabbitmq_handler.auto_reconnect)


async def idi_server_connect(re_conn=True, conn_time=None):
    import motor

    __cnt = 0
    while True:
        try:
            if conn_time and __cnt == int(conn_time):
                get_logger().error(
                    "Idi server mongodb async connect error,connect more than "
                    + str(conn_time)
                    + " times"
                )
                raise MongodbConnectError
            store = Store.get_init_task_config(TaskKey.idi_server)
            mongodb_host = store.get_mongodb_host()
            mongodb_port = int(store.get_mongodb_port())
            connector = motor.motor_tornado.MotorClient(mongodb_host, mongodb_port)
            get_logger().info("Idi server mongodb async connected successfully")
            set_handler(TaskKey.idi_server, connector)

            return connector
        except Exception:
            __cnt += 1
            get_logger().exception(
                "Idi server mongodb async connecting retry number: " + str(__cnt)
            )
            store = Store.get_init_task_config(TaskKey.idi_server)
            await gen.sleep(store.retry_interval())


class MgDbEnvironmentError(Exception):
    pass


#


class MgTestCase(AsyncTestCase):
    __is_init = False

    def set_up_class(self) -> None:
        if not self.__class__.__is_init:
            Store.set_loop(self.io_loop)
            set_store(ConfigStore())
            Store.set_tasks(TASKS)
            update_context("debug_flag", True)
            update_context("organization", {"code": "mes"})
            update_context(
                "rabbitmq",
                {
                    "host": "192.168.20.134",
                    "port": "5672",
                    "username": "rabbitmq",
                    "password": "123456",
                },
            )
            update_context("mongodb_cluster", {"host": "192.168.20.120", "port": "27010"})
            set_init_task(
                [
                    {TaskKey.idi_server: IdiServerConfigBasic()},
                    {TaskKey.rabbitmq_async: RabbitMQConfig()},
                    {TaskKey.redis_async: RedisConfigBasic()},
                    {TaskKey.init_func: InitFuncConfig()},
                ]
            )

            self.io_loop.run_sync(idi_server_connect)
            self.io_loop.run_sync(redis_async_connect)
            self.io_loop.run_sync(rabbitmq_async_connect)
        self.__class__.__is_init = True

    def setUp(self):
        super().setUp()
        self.set_up_class()

    def tearDown(self):
        pass


if __name__ == "__main__":
    import sys
    import unittest


    def import_string(import_name, silent=False):
        """flask 中 werkzeug的模块"""
        import_name = str(import_name).replace(":", ".")
        try:
            try:
                __import__(import_name)
            except ImportError:
                if "." not in import_name:
                    raise
            else:
                return sys.modules[import_name]

            module_name, obj_name = import_name.rsplit(".", 1)
            module = __import__(module_name, globals(), locals(), [obj_name])
            try:
                return getattr(module, obj_name)
            except AttributeError as e:
                raise ImportError(e)

        except ImportError as e:
            if not silent:
                raise ImportError(e)


    def iter_suites():
        import os

        result = []
        for mod_name in os.listdir("."):
            if mod_name and mod_name.startswith("test_"):
                mod = import_string(mod_name.strip(".py"))
                if hasattr(mod, "suite"):
                    yield mod.suite()
        return result


    def suite():
        suite = unittest.TestSuite()
        for other_suite in iter_suites():
            suite.addTest(other_suite)
        return suite


    unittest.main(defaultTest="suite")
